#  Copyright (C) 2010 Luca Piccinelli
# 
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

'''
Created on Jun 3, 2011

@author: picci
'''
from PyQt4 import QtGui, QtCore
from pypelab.ui.pypeWidgets import PDLWidget, ComputationWidget, IOKeyLabel, IOKeyGroup
from pipeit import gestface_exception, AbsPipeStepDescrConsumer, bad_class, GenericPDSVisitor, PipelineDescriptor_castTo, GeneratorFactoriesPool_get_instance
from pypelab.ui.visitors.pdlvisitors import QtGuiPDLVisitor
from pypelab.ui.pypeWidgets import PipeWorkAreaMdiSubWindow
from pypelab.dataStructures import IOKeyGroupNode, GraphArc
from pypelab import globals
from pypelab.common.utils import IdGenerator
import logging

def build_pdl_widget(wid, visitor, key):
    #****** ComputationWidgetBuilder*************************************
    """ This function calls the visitor or add a LineEdit if the visitor
        fails """
    #********************************************************************
    try:
#        visitor.set_layout_address(sip.unwrapinstance(wid.verticalLayout))
        visitor.qt_element = wid.verticalLayout
        key.getAcceptedValues().serialize(visitor)
    except gestface_exception:
        edit = QtGui.QLineEdit()
        edit.setMinimumWidth(100)
        wid.verticalLayout.addWidget(edit)
            
    wid.connect()

class ComputationWidgetBuilder(AbsPipeStepDescrConsumer):
    #****** ComputationWidgetBuilder*************************************
    """ The delegate that analysis the computation and build up the    
        computation widget interface """
    #********************************************************************
    def __init__(self, _parent = None, _position = None):
        super().__init__()
        self.parent = _parent
        self.lastBuilt = None
        self.position = _position
    
    def consume(self, pipelineDescriptor):
        steps_descriptors = PipelineDescriptor_castTo(pipelineDescriptor).get_computation_steps() 
        
        for iter_pipeStepDescriptor in steps_descriptors:
            # if i don't clone this object, when the Pipeline descriptor is destroyed  all its internal descriptors are destroyed too
            pipeStepDescriptor = iter_pipeStepDescriptor.clone() 
            computation_id = pipeStepDescriptor.get_computation_name()
        
            # setup the subwindow and the widget
            window = PipeWorkAreaMdiSubWindow(globals.pipeWorkArea)
                
            self.lastBuilt = compWid = ComputationWidget(window)
            if not self.buildUi(pipeStepDescriptor, compWid): return False
            if self.parent: self.parent.addSubWindow(window)
        
            window.setWidget(compWid)
            window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
            window.adjustSize()
            window.setWindowTitle(computation_id)
            if self.position: window.move(self.position)
            window.show()
            
        # valorize widgets    
        windows = globals.pipeWorkArea.subWindowList()  
        for i, iter_pipeStepDescriptor in enumerate(steps_descriptors):
            pipeStepDescriptor = iter_pipeStepDescriptor.clone()
            self.valorizeUi(windows[i].widget(), pipeStepDescriptor)
            windows[i].adjustSize()

        # connect widgets            
        for i, iter_pipeStepDescriptor in enumerate(steps_descriptors):
            pipeStepDescriptor = iter_pipeStepDescriptor.clone()
            self.connectWidgets(windows[i].widget(), pipeStepDescriptor)    
            
        return True 
        
    def buildUi(self, pipeStepDescriptor, compWid = None):
        if not compWid: self.lastBuilt = compWid = ComputationWidget()
        compWid.pipeStepDescriptor = pipeStepDescriptor
        
        # try to instantiate the computation class
        try:
            pds_visitor = GenericPDSVisitor()
            pipeStepDescriptor.get_computation(pds_visitor)
            computation = pds_visitor.get_computation()
        except bad_class as ex:
            QtGui.QMessageBox.warning(self, "No Implementation found", 'No implementation found for {0} operation'.format(pipeStepDescriptor.get_computation_name()))
            logging.error(ex.what())
            return False
        
        # Criteria setup
        criteria_area = compWid.ui.criteria_area
        criteria_area_layout = QtGui.QGridLayout()
        criteria_area.setLayout(criteria_area_layout)
        
        # append a new widget for each criteria key 
        pdl_visitor = QtGuiPDLVisitor()
        for i, key in enumerate(computation.getCriteriaKeysList()):
            label = IOKeyLabel(key, compWid.pipeStepDescriptor, compWid, criteria_area)
            compWid.criteria_labels.append(label)
            criteria_area_layout.addWidget(label, i, 1)
            
            pdl_widget = PDLWidget(criteria_area)
            criteria_area_layout.addWidget(pdl_widget, i, 2)
            label.setBuddy(pdl_widget)
            build_pdl_widget(pdl_widget, pdl_visitor, key)
            
            # valorize the control from the descriptor
            value = '0'
            try:
                value = pipeStepDescriptor.get_criteria(key)
            except:
                pipeStepDescriptor.set_criteria(key, value)
            pdl_widget.setValue(value)
            
        # Input setup
        input_area = compWid.ui.input_area
        input_area_layout = QtGui.QGridLayout()
        input_area.setLayout(input_area_layout)
        
        for i, key in enumerate(computation.getInputKeysList()):
            label = IOKeyLabel(key, compWid.pipeStepDescriptor, compWid, input_area)
            label.setAcceptDrops(True)
            compWid.in_labels.append(label)
            input_area_layout.addWidget(label, i, 1)
            compWid.in_types.append(key.getType())
            
        # Output setup
        output_area = compWid.ui.output_area
        output_area_layout = QtGui.QGridLayout()
        output_area.setLayout(output_area_layout)
        
        for i, key in enumerate(computation.getOutputKeysList()):
            label = IOKeyLabel(key, compWid.pipeStepDescriptor, compWid, output_area)
            label.setAcceptDrops(True)
            compWid.out_labels.append(label)
            output_area_layout.addWidget(label, i, 1)
            compWid.out_types.append(key.getType())    
            
        return True
    
    def valorizeUi(self, compWid, pipeStepDescriptor):
        for label in compWid.in_labels:
            self.buildGroup(label, pipeStepDescriptor)
        for label in compWid.out_labels:
            self.buildGroup(label, pipeStepDescriptor)
    
    def buildGroup(self, keyLabel, descriptor):
        try:
            if keyLabel in keyLabel.mainContainer.in_labels:
                keyLabel.generator_id, conf = descriptor.get_in_gen(keyLabel.key)
            elif keyLabel in keyLabel.mainContainer.out_labels:
                keyLabel.generator_id, conf = descriptor.get_out_gen(keyLabel.key)
            else:
                return
                
            # create the group 
            keyLabel.group = IOKeyGroup(keyLabel, keyLabel.generator_id, keyLabel.parent())
            # valorize group labels
            for key, label in keyLabel.group.confItems():
                type = key.getType()
                value_wid = label.buddy()
                if type == globals.INT:
                    value_wid.setValue(str(conf.get_int_parameter(key)))
                elif type == globals.LONG or type == globals.MEMORYSZ:
                    value_wid.setValue(str(conf.get_long_parameter(key)))
                elif type == globals.FLOAT:
                    value_wid.setValue(str(conf.get_float_parameter(key))) 
                elif type == globals.DOUBLE:
                    value_wid.setValue(str(conf.get_double_parameter(key)))
                elif type == globals.STRING:
                    value = conf.get_string_parameter(key)
                    value_wid.setValue(str(value))
                         
                elif type() == globals.PARAM_IOKEY_TYPE:
                    pass
                else:
                    raise RuntimeError("Unsupported type {0} for Configuration element {1}".format(type, key.get_key()))
                 
        except RuntimeError as err:
            logging.info("{0} for IOKey {1} while trying to build an IOKeyGroup".format(err, keyLabel.key.get_key()))
          
            
    def connectGroups(self, sourceLabel, targetLabel):
        source_wid = sourceLabel.mainContainer
        target_wid = targetLabel.mainContainer
        
        source_container = source_wid.parent()
        target_container = target_wid.parent() 
        
        start_node = IOKeyGroupNode(sourceLabel.text(), 
                                    source_container.rect(), 
                                    source_container.pos(), 
                                    sourceLabel.mapTo(source_wid, sourceLabel.pos()))
        end_node   = IOKeyGroupNode(targetLabel.text(), 
                                    target_container.rect(), 
                                    target_container.pos(), 
                                    targetLabel.mapTo(target_wid, targetLabel.pos()))
        arc = GraphArc([start_node, end_node])
        
        # register arc and nodes to events of the GUI like move of widgets and
        # closing of groups 
        source_container.addMoveObserver(start_node)
        target_container.addMoveObserver(end_node)
                        
        arc.addObserver(targetLabel)
        arc.addObserver(target_wid)
        arc.addObserver(sourceLabel)
        arc.addObserver(source_wid)
        arc.addObserver(globals.pipeWorkArea)
        
        sourceLabel.group.addCloseObserver(targetLabel.group)
        source_wid.addCloseObserver(targetLabel.group)
        
        globals.pipeWorkArea.viewport().update()
        
    def connectWidgets(self, compWid, pipeStepDescriptor):
        idGenerator = IdGenerator()
        try:
            for i, target_label in enumerate(compWid.in_labels + compWid.out_labels):
                if i < len(compWid.in_labels):
                    target_label.generator_id, conf = pipeStepDescriptor.get_in_gen(target_label.key)
                else:
                    target_label.generator_id, conf = pipeStepDescriptor.get_out_gen(target_label.key)
            
                # valorize group labels
                for key, label in target_label.group.confItems():
                    type = key.getType()
                    if type == globals.STRING and key.get_key() == globals.GENERATOR_ID_IOKEY:
                        value_wid = label.buddy()
                        value = conf.get_string_parameter(key)
                        value_wid.setEnabled(False)
                        pipe_id, step_pipe_id, source_gen_key = value.split(globals.PIPESTEP_ID_SEP)
                        idGenerator.registerID(step_pipe_id)
                        step_id = pipe_id + globals.PIPESTEP_ID_SEP + step_pipe_id
                        source_wid = None
                        # search for the ComputationWidget with the given id
                        for subWnd in globals.pipeWorkArea.subWindowList():
                            if subWnd.widget().id() == step_id: 
                                source_wid = subWnd.widget()
                                
                        if not source_wid:
                            raise RuntimeError("Not found any widget with the given id {0} in the computation area".format(step_id)) 
                        
                        source_label = None
                        keys = source_wid.keys()
                        for k in keys.keys():
                            if k.get_key() == source_gen_key: source_label = keys[k]
                          
                        if not source_wid:
                            raise RuntimeError("Not found any label with the given key {0} in the widget {1}".format(source_gen_key, step_id))  
                        
                        # create linking arc between the two widget groups
                        self.connectGroups(source_label, target_label)
        except RuntimeError as err:
            logging.info("{0} for IOKey {1} while trying to connect an IOKeyGroup".format(err, target_label.key.get_key()))                        